#include "OSGRender.h"

#include <osg/Geode>
#include <osg/Geometry>
#include <osg/ShapeDrawable>
#include <osg/MatrixTransform>
#include <osg/Material>
#include <osg/CullFace>
#include <osg/Texture2D>
#include <osg/BlendFunc>

#include <osg/ComputeBoundsVisitor>
#include <osg/TriangleFunctor>
#include <osg/TriangleIndexFunctor>

#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgGA/TrackballManipulator>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/api/Win32/GraphicsWindowWin32>

#include "util.h"
#include "osgQtWidget.h"

static const osg::StateAttribute::OverrideValue override_on = osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE;
static const osg::StateAttribute::OverrideValue override_off = osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE;

class ComputeTexcoordVisitor : public osg::NodeVisitor
{
public:
    ComputeTexcoordVisitor()
    {
        setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
    }
    virtual void apply(osg::Geometry& geometry)
    {
        osg::ComputeBoundsVisitor cbv;
        geometry.accept(cbv);
        osg::BoundingBox bb = cbv.getBoundingBox();
        float width = bb.xMax() - bb.xMin();
        float height = bb.zMax() - bb.zMin();

        osg::Vec2Array* texArray = dynamic_cast<osg::Vec2Array*>(geometry.getTexCoordArray(0));
        osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geometry.getVertexArray());
        if (texArray && verts && texArray->size() == verts->size())
        {
            for (int i = 0; i != texArray->size(); ++i)
            {
                osg::Vec3 vert = verts->at(i);
                float u = (vert.x() - bb.xMin()) / width;
                float v = (vert.z() - bb.zMin()) / height;
                texArray->at(i).set(u, v);
            }
        }
    }
};

OSGRender::OSGRender()
{
    // init params first
    mWidget = NULL;
    mRoot = new osg::Switch;
    mOffRoot = new osg::Group;
    mViewer = new osgViewer::Viewer;
    mOffViewer = new osgViewer::Viewer;

    initViewer();
    initOffscreenViewer();

    initScene();

    mViewer->home();
}

OSGRender::~OSGRender()
{
}

void OSGRender::setWidget(OSGWidget* w)
{
    mWidget = w;
    osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(
        mViewer->getCamera()->getGraphicsContext());
    if (gw)
    {
        mWidget->setGraphicsWindow(gw);

        setViewer(mViewer, mWidget);
    }
}

osg::Node* OSGRender::computeAndRenderTexture(osg::Node* block, osg::Group* scene)
{
    if (block==NULL || scene==NULL)
    {
        return NULL;
    }

    mOffRoot->removeChildren(0, mOffRoot->getNumChildren());
    mOffRoot->addChild(scene);
    const osg::Camera::BufferAttachmentMap& attachMap = mOffViewer->getCamera()->getBufferAttachmentMap();
    osg::Camera::BufferAttachmentMap::const_iterator citr = attachMap.find(osg::Camera::COLOR_BUFFER0);
    if (citr != attachMap.cend())
    {
        const osg::Camera::Attachment& attach = citr->second;
        if (attach._image->valid())
        {
            osg::ComputeBoundsVisitor cbv;
            scene->accept(cbv);
            osg::BoundingBox box = cbv.getBoundingBox();
            float width = box.xMax() - box.xMin();
            float height = box.zMax() - box.zMin();
            attach._image->allocateImage(width*10, height*10, 1, attach._image->getPixelFormat(), attach._image->getDataType(), attach._image->getPacking());
            mOffViewer->getCamera()->setViewport(0, 0, width*10, height*10);
            mOffViewer->getCamera()->setViewMatrixAsLookAt(box.center() + osg::Vec3(0, -1, 0), box.center(), osg::Vec3(0, 0, 1));
            mOffViewer->getCamera()->setProjectionMatrixAsOrtho(-width / 2.f, width / 2.f, -height / 2.f, height / 2.f, box.yMin() - 1, box.yMax() + 1);
            
            double start = osg::Timer::instance()->time_s();
            mOffViewer->frame();
            double end = osg::Timer::instance()->time_s();
            std::cout << end - start << std::endl;
            osg::ref_ptr<osg::Image> img = (osg::Image*)attach._image->clone(osg::CopyOp::DEEP_COPY_ALL);
            osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
            tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
            tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
            tex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
            tex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
            tex->setImage(img);

            osg::Node* newBlock = (osg::Node*)block->clone(osg::CopyOp::DEEP_COPY_ALL);
            newBlock->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex, override_on);
            ComputeTexcoordVisitor ctv;
            newBlock->accept(ctv);
            return newBlock;
        }
    }
    return NULL;
}

bool OSGRender::initViewer()
{
    osg::DisplaySettings::instance()->setNumMultiSamples(16);
    osg::DisplaySettings::instance()->setMinimumNumStencilBits(8);

    osg::Camera* master = new osg::Camera;

    osg::ref_ptr<osg::Image> img = new osg::Image;
    img->allocateImage(512, 512, 1, GL_RGBA, GL_UNSIGNED_BYTE);
    master->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
    master->attach(osg::Camera::COLOR_BUFFER0, img);
    
    master->setClearColor(osg::Vec4(0, 1, 1, 1));
    master->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    master->setViewport(new osg::Viewport(0, 0, 512, 512));
    master->setProjectionMatrixAsPerspective(40.f, 1.f, 1.f, 1000.f);
    master->setViewMatrixAsLookAt(osg::Vec3(0, 10, 0), osg::Vec3(0, 0, 0), osg::Vec3(0, 0, 1));

    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(osg::DisplaySettings::instance());
    traits->x = 0;
    traits->y = 0;
    traits->width = 512;
    traits->height = 512;
    traits->stencil = 8;
    traits->doubleBuffer = true;
    //traits->windowDecoration = true;
    traits->sharedContext = 0;
    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits);
    master->setGraphicsContext(gc);
    gc->setResizedCallback(new OSGRenderResizedCallback(master));
    master->getOrCreateStateSet()->setMode(GL_BLEND, override_on);
    master->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, override_on);
    mViewer->setCamera(master);
    mViewer->setSceneData(mRoot);
    mViewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded); // still... 
    mViewer->setCameraManipulator(new osgGA::TrackballManipulator);
    mViewer->realize();

    osgViewer::GraphicsWindowWin32* windowWin32 =
        dynamic_cast<osgViewer::GraphicsWindowWin32*>(gc.get());
    if (windowWin32)
    {
        HWND hwnd = windowWin32->getHWND();
        ::ShowWindow(hwnd, SW_HIDE);
    }

    return true;
}

void OSGRender::initOffscreenViewer()
{
    osg::Camera* master = new osg::Camera;
    osg::ref_ptr<osg::Image> img = new osg::Image;
    img->allocateImage(512, 512, 1, GL_RGBA, GL_UNSIGNED_BYTE);
    master->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
    master->attach(osg::Camera::COLOR_BUFFER0, img);

    master->setClearColor(osg::Vec4(0, 0, 0, 0));
    master->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    master->setViewport(new osg::Viewport(0, 0, 512, 512));
    master->setProjectionMatrixAsPerspective(40.f, 1.f, 1.f, 1000.f);
    master->setViewMatrixAsLookAt(osg::Vec3(0, 10, 0), osg::Vec3(0, 0, 0), osg::Vec3(0, 0, 1));

    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(osg::DisplaySettings::instance());
    traits->x = 0;
    traits->y = 0;
    traits->width = 1;
    traits->height = 1;
    traits->stencil = 8;
    traits->doubleBuffer = true;
    traits->windowDecoration = true;
    traits->sharedContext = 0;
    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits);
    master->setGraphicsContext(gc);
    master->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, override_on);
    mOffViewer->setCamera(master);
    mOffViewer->setSceneData(mOffRoot);
    mOffViewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
    mOffViewer->realize();

    osgViewer::GraphicsWindowWin32* windowWin32 =
        dynamic_cast<osgViewer::GraphicsWindowWin32*>(gc.get());
    if (windowWin32)
    {
        HWND hwnd = windowWin32->getHWND();
        ::ShowWindow(hwnd, SW_HIDE);
    }
}

void OSGRender::initScene()
{
    osg::ref_ptr<osg::Group> originScene = new osg::Group;
    osg::ref_ptr<osg::Group> texturedScene = new osg::Group;
    mRoot->addChild(originScene);
    mRoot->addChild(texturedScene);

    osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("plane.osgt");
    // make origin scene
    {
        osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
        tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
        tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
        tex->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
        tex->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
        osg::ref_ptr<osg::Image> img = osgDB::readImageFile("Images/osg256.png");
        if (img->valid())
        {
            tex->setImage(img);
        }
        else 
        {
            __asm int 3;
        }
        osg::ref_ptr<osg::Group> nodeGroup = new osg::Group;
        nodeGroup->addChild(node);
        originScene->addChild(nodeGroup);

        osg::ref_ptr<osg::ShapeDrawable> sd = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0, 0, 0), 10));
        osg::ref_ptr<osg::Geode> geode = new osg::Geode;
        geode->addDrawable(sd);
        osg::ref_ptr<osg::Material> mat = new osg::Material;
        mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(1,0,0,0.5));
        geode->getOrCreateStateSet()->setAttributeAndModes(mat, override_on);
        geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex, override_off);
        originScene->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex, override_on);
        geode->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK), override_on);

        originScene->addChild(geode);
        originScene->getOrCreateStateSet()->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), override_on);
    }
    // make textured scene
    {
        osg::ref_ptr<osg::Node> geode = computeAndRenderTexture(node, originScene);
        if (geode.valid())
        {
            osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
            mt->addChild(geode);
            mt->setMatrix(osg::Matrix::translate(0, 30, 0));
            texturedScene->addChild(mt);
        }
        texturedScene->getOrCreateStateSet()->setMode(GL_LIGHTING, override_off);
        texturedScene->getOrCreateStateSet()->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), override_on);
    }
}

